//Copyright (C) 2012 Chirila Alexandru
//This file is part of Mirror FTP
//
//Mirror FTP is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//Mirror FTP is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with Mirror FTP.  If not, see <http://www.gnu.org/licenses/>.

#include "ftp.h"


struct Time{
	///structure is sent in new thread used for refresh time out

	int *refr;
	int rate;
};

ftp::ftp(){
	fl=NULL;
	refr=0;
	refr_rate=0;
}

ftp::~ftp(){}

int ftp::closeAll(){
	///Logout and close all connections

	notify("Loging Out");
	cmd.send_cmd("QUIT","");
	cmd.closeC();
	dta.closeC();
	notify(" --> OK \n");
	return 0;
}

int ftp::resolve(){
	///Resolve hosts name.

	notify("Resolving host " + addrServer);
	in_addr_t **addr_list;
	in_addr i;

	host=*gethostbyname(addrServer.c_str());
	if (h_errno!=1){
		notify("\t:ERR - Unable to resolve host\n");
		return -1;
	}
	addr_list=(in_addr_t**)host.h_addr_list;
	i.s_addr=*addr_list[0];
	server_address=inet_ntoa(i);
	notify(" --> OK \n");
	return 0;
}

int ftp::connect(int port){
	///Connect to resolved host and to inputed port number.
	c_port=port;
	notify("Connecting to port");
	if (cmd.establish(host,port)!=0){
		notify("\t:ERR - "+cmd.get_last_resp()+"\n");
		return -1;
	}
	welcome=cmd.get_last_resp();
	notify(" --> OK \n");
	return 0;
}

int ftp::login(string user,string pass){
	///Login to ftp server using  user and password. Should be preceded by a successful connection.
	c_user=user;
	c_pass=pass;
	notify ("Login");
	if (cmd.login(user,pass)!=0){
		notify("\t:ERR - "+cmd.get_last_resp()+"\n");
		return -1;
	}
	notify(" --> OK \n");
	return 0;
}

int ftp::connect_dta_active(){
	///Establishes an active data connection.

	if (dta.establish_active(host)!=0)
		return -1;
	if(cmd.send_port(dta.get_port())!=0)
		return -2;
	return 0;
}

int ftp::connect_dta_passive(){
	///Establishes an passive data connection

	if (cmd.send_cmd("PASV","")!=0)
		return -1;

	if (cmd.get_reply_int(cmd.get_last_resp())!=227){
		//Fatal Error... Confused server
		//Stop any ongoing operations

		stop=1;
		empty_queue();
		notify("\t:Fatal Error - Increase Time Out or use Active Mode instead\n");
		return -3;
	}

	if (dta.establish_passive(cmd.get_last_resp())!=0)
		return -2;
	return 0;
}

int ftp::send_receive(char data_mode,long long size,
		string command,string argument,string file,char data_type){
	///Send command and await response from data connection.
	///\param data_mode data transfer mode \n 'A' - active \n 'P' - passive
	///\param size the size of the downloaded file ( 0 if unknown )
	///\param command the server command
	///\param argument the command's argument
	///\param file the file where the data is saved
	///\param data_type the type of data being received : \n 'A' - Ascii \n 'I' - Binary (Image) \n if not sure leave Binary mode
	///\return 0 in case of succes
	///\return -3 in case of canceled operation

	notify(command+" "+argument);
	int ret;
	string s;
	stringstream ss,ss2;
	string size_s;

	ss << data_type;
	ss >> s;

	if (cmd.send_cmd("TYPE",s)!=0){
		notify("\t:ERR - "+cmd.get_last_resp()+"\n");
		return -5;
	}

	if (size>0){
		if (size<(1024*1024)){
			ss2 << size/1024;
			ss2 >> size_s;
			size_s=" ("+size_s+" Kb)";
		}
		else{
			ss2 << size/(1024.0*1024.0);
			ss2 >> size_s;
			size_s=" ("+size_s+" Mb)";
		}
	}

	notify_f(argument+size_s);

	if (data_mode=='a' || data_mode=='A'){
		if (connect_dta_active()!=0){
			notify("\t:ERR - "+cmd.get_last_resp()+"\n");
			return -1;
		}
		if (cmd.send_cmd(command,argument)!=0){
			notify("\t:ERR - "+cmd.get_last_resp()+"\n");
			return -2;
		}
		ret=dta.listen_active(this,size,file,data_type);
		if (ret==-1){
			notify("\t:ERR - canceled\n");
			return -3;
		}
		else if(ret<-1) {
			notify("\t:ERR - "+cmd.get_last_resp()+"\n");
			return -4;
		}
		cmd.read_more();
	}
	else{
		if (connect_dta_passive()!=0){
			notify("\t:ERR - "+cmd.get_last_resp()+"\n");
			return -1;
		}
		if (cmd.send_cmd(command,argument)!=0){
			notify("\t:ERR - "+cmd.get_last_resp()+"\n");
			return -2;
		}
		ret=dta.listen_passive(this,size,file,data_type);
		if (ret==-1){
			notify("\t:ERR - canceled\n");
			return -3;
		}
		else if(ret<0) {
			notify("\t:ERR - "+cmd.get_last_resp()+"\n");
			return -4;
		}
		cmd.read_more();
	}
	dta.closeC();
	notify(" --> OK \n");
	return 0;
}

int ftp::send (string command,string argument,bool b_notify){
	///Send only a command wait for no response from data connection
	///\param b_notify if false the command is silent

	if (b_notify) notify(command+" "+argument);

	if( cmd.send_cmd(command,argument)!=0){
		notify("\t:ERR - "+cmd.get_last_resp()+"\n");
		return -1;
	}
	if (b_notify) notify(" --> OK \n");
	return 0;
}

void ftp::refresh_all(char data_mode){
	///Goes through file nodes allready downloaded and checks MDTM (last mod date).
	///If they are not up to date re-download and mark new MDTM.

	notify("--Starting Periodical Refresh Procedure\n");
	notify("-------------------------------------------------------------------------|\n");
	notify_f("Press Cancel to End Refresh");
	queue<file_node*> que;
	file_node *node;
	list<file_node*>::iterator it;
	string mdtm,n;
	int end,begin;

	que.push(fl);
	while(!que.empty()){		//BFS
		if (stop==1)
			break;
		node=que.front();
		if (node->get_type()=='f'){
			notify("---");
			send("MDTM",node->get_path());
			mdtm=get_last();
			begin=mdtm.find_first_of(' ',0)+1;
			end=mdtm.find_first_of(' ',begin);
			mdtm=mdtm.substr(begin,end);


			if (mdtm!=node->get_mdtm()){
				n=addrServer+node->get_path();
				notify_f("Re -> "+node->get_name());
				notify("\t--Re: ");
				if (send_receive(data_mode,node->get_size(),"RETR",node->get_path(),n,'I')==0)
					node->set_mdtm(mdtm);
				usleep(time_out*1000);
				notify_f("Press Cancel to End Refresh");
			}
		}
		for (it=node->children.begin();it!=node->children.end();++it){
			que.push(*it);
		}
		que.pop();
	}
	stop=0;
	refr=0;
	notify("-------------------------------------------------------------------------|\n");
}

void *ftp::timer(void *arg){
	///Static function (used in a new thread).
	///Count to refresh time out value and marks refr,
	///After that it awaits refr to be reinitialized so it can restart countdown
	///\param arg reference to a Time structure

	Time *t;
	int r;
	t=(Time*)arg;
	while(1){
		r=*t->refr;
		if (r==0){
			for (int i=0;i<t->rate;i++){
				sleep(1);
			}
			*t->refr=1;
		}
	}
	return NULL;
}

void ftp::empty_queue(){
	///Empty's the queue and curent file node structure
	if (fl!=NULL)
		delete fl;
	fl=NULL;
	while (!Q.empty())
		Q.pop();
}

void ftp::dump_to_xml(string file){
	///dumps the current status of the recursive download algorithm
	///to an xml format so it can be continued later

	fstream out;
	string x,p;
	stringstream ss;
	out.open(file.c_str(),ios::out);

	ss << c_port;
	ss >> p;

	x="<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";
	out.write(x.c_str(),x.length());
	x="<mftp>";
	out.write(x.c_str(),x.length());
	x="<server addr=\""+addrServer+"\" port=\""+p+"\" user=\""+c_user+"\" pass=\""+c_pass+"\" >\n";
	out.write(x.c_str(),x.length());
	fl->dump_to_xml(&out);
	x="</server>";
	out.write(x.c_str(),x.length());
	x="</mftp>";
	out.write(x.c_str(),x.length());
	out.close();

	delete fl;
	fl=NULL;
}

int ftp::open_from_xml(string file){
	///opens from a xml file a download status and prepares
	///the queue and the file_node structure to continue the download

	pugi::xml_document doc;
	pugi::xml_node node,it;
	string p;

	if (!doc.load_file(file.c_str())) return -1;
	node=doc.child("mftp").child("server");

	set_address(node.attribute("addr").value());
	if (resolve()!=0) return -2;

	if (connect(node.attribute("port").as_int())!=0) return -3;
	if (login(node.attribute("user").value(),node.attribute("pass").value())!=0) return -4;
	empty_queue();
	fl=new file_node;
	xml_node_proc(node.first_child(),fl);
	return 0;
}
void ftp::xml_node_proc(pugi::xml_node node,file_node *f){
	///recursive process of xml nodes and creating tree

	pugi::xml_node it;
	queue<pugi::xml_node> que;
	file_node *temp_child;
	char type=node.name()[0];
	if (node.attribute("check").as_int()==1)
		f->checked();
	else
		Q.push(f);

	switch (type){
	case 'f':
		f->set_type(type);
		f->set_name(node.attribute("name").value());
		f->set_path(node.attribute("path").value());
		f->set_size(node.attribute("size").as_int());
		f->set_mdtm(node.attribute("mdtm").value());
		break;
	case 'd':
	case 'm':
		f->set_type(type);
		f->set_name(node.attribute("name").value());
		f->set_path(node.attribute("path").value());
		for (it=node.first_child();it;it=it.next_sibling()){
			temp_child=new file_node;
			f->add_child(temp_child);
			xml_node_proc(it,temp_child);
		}
		break;
	}


}


void ftp::download_all(char data_mode,char list_mode,string path){
	///Starts loading file tree and download all from current directory
	///\param data_mode data transfer mode \n 'A' - active \n 'P' - passive
	///\param list_mode data listing mode  \n 'L' - normal \n 'M' - machine

	Time *arg=(Time*)malloc(sizeof(Time));
	int ok,resume;
	unsigned int begin,end;
	string x;
	file_node *child;
	file_node *temp;
	fstream input;
	mode_t md=S_IRWXU | S_IRWXG | S_IRWXO;

	char *tmp;
	string n;

	if (refr_rate!=0){
		arg->refr=&refr;
		arg->rate=refr_rate;
		pthread_create(&thread,NULL,&ftp::timer,arg);
	}

	stop=0;

	//if file list (fl) is null create new root node and insert it into the queue
	//if it is not null, the download operation was canceled and now it can be resumed
	if (fl==NULL){
		fl=new file_node;	//root
		fl->set_name(path);
		fl->set_path(path);
		fl->set_type('d');
		begin=-1;
		mkdir(addrServer.c_str(),md); //make top directory with server name
		x="";
		do{
			begin=path.find_first_of('/',begin)+1;
			end=path.find_first_of('/',begin);
			x+=path.substr(begin,end-begin)+"/";
			if (x.length()>0)
				mkdir((addrServer+"/"+x).c_str(),md);
		}while(begin!=path.length());

		Q.push(fl);			//push root in queue

	}

	tmp=(char*)malloc(256);
	while (!Q.empty()){ 	//BFS
		if (stop==1) //operation was canceled
			break;

		temp=Q.front();		//pop front

		if (temp->is_checked())
			resume=0;
		else
			resume=1;

		switch (list_mode){
		case 'M':
			x="MLSD";
			break;
		case 'L':
		default:
			x="LIST";
			break;

		}

		//get file list of curent directory, if operation was canceled exit from while
		ok=send_receive(data_mode,0,x.c_str(),temp->get_path(),"fl.out",'A');
		if(ok==-3)	break;	//canceled
		if(ok!=0)	{		//error at list
			Q.pop();
			continue;
		}
		usleep(time_out*1000);

		if(refr==1){
			refresh_all(data_mode);
		}

		input.open("fl.out",ios::in);
		while(1){			//go trough every item in listing
			if (stop==1)	//operation was canceled
				break;
			bzero(tmp,256);
			input.getline(tmp,256);
			switch (list_mode){
			case 'M':
				ok=analyse_machine(temp,tmp);
				break;
			case 'L':
			default:
				ok=analyse_normal(temp,tmp);
				break;

			}

			if (ok==-1)
				break;			//end of file
			else if (ok==1)
				continue;		//bogus line

			child=*(--temp->children.end());
			n=addrServer+child->get_path();

			//check if file was already downloaded
			if ((resume==1) && (temp->check_in(child->get_path())==1)){
				temp->children.pop_back();
				continue;
			}

			//if directory or mirror add to queue and make directory
			if (child->get_type()=='d' || child->get_type()=='m'){
				Q.push(temp->children.back());
				mkdir(n.c_str(),md);
			}
			//if file download
			else if (child->get_type()=='f'){

				//if canceled during download remove from parent list
				//this way it will be marked as not downloaded, when and if
				//the user resumes and the download will be completed
				ok=send_receive(data_mode,child->get_size(),"RETR",child->get_path(),n,'I');
				if(ok==-3){
					temp->children.pop_back();
				}
				else{
					child->checked();
				}
				usleep(time_out*1000);
			}
			if (refr==1)
				refresh_all(data_mode);
		}	//end going trough list
		input.close();

		//if operation was canceled don't pop the queue so the current dir will
		//be reanalyzed by the application when and if the users resumes
		if (stop!=1){
			Q.pop();	//pop current directory from queue
			temp->checked();
		}

	}
	if (refr_rate!=0)
		pthread_cancel(thread);
	if (stop!=1)
		empty_queue();
}

int ftp::analyse_machine(file_node *temp,char *tmp){
	///analize current line from file, generated with
	///the command "MLSD" suported by some FTP servers,
	///separating the fields and adding a child to the file_node *temp


	string line,path,name,mdtm,x;
	char type;
	long long s;
	int poz_i, poz_f;
	line=tmp;
	if (line.length()<=1)	//EOF
		return -1;

	poz_i=line.find_last_of(';',-1)+2;
	name=line.substr(poz_i);

	path=temp->get_path()+name;

	poz_i=line.find("type=",0)+5;
	poz_f=line.find_first_of(';',poz_i);
	x=line.substr(poz_i,poz_f-poz_i);

	if (x=="dir"){
		path+="/";
		type='d';
		s=0;
		mdtm="";
	}
	else if (x=="file"){
		type='f';
		poz_i=line.find("size=",0)+5;
		poz_f=line.find_first_of(';',poz_i);
		s=atoi(line.substr(poz_i,poz_f-poz_i).c_str());
		poz_i=line.find("modify=",0)+7;
		poz_f=line.find_first_of(';',poz_i);
		send("MDTM",path,0);
		mdtm=get_last();
		int begin,end;
		begin=mdtm.find_first_of(' ',0)+1;
		end=mdtm.find_first_of(' ',begin);
		mdtm=mdtm.substr(begin,end);
	}
	else
		return 1;
	//cout << type << name << path << s << mdtm << endl;
	temp->add_child(type,name,path,s,mdtm);

	return 0;
}


int ftp::analyse_normal(file_node *temp,char *tmp){
	///analize current line from file, generated with
	///the command "LIST" suported by all FTP servers,
	///separating the fields and adding a child to the file_node *temp

	string line,path,n,mdtm;
	int poz_i,poz_f,count;
	long long s;
	char t;

	line=tmp;
	if (line.length()<=1)	//EOFstring line,aux,n,mdtm;
		return -1;

	count=0;		//check for bogus line
	for (unsigned int i=0;i<strlen(tmp);i++)
		if (tmp[i]==' ')
			count++;
	if (count < 8 )
		return 1;

	path=line.substr(0,1);	//get node type
	if(path=="d")
		t='d';
	else
		t='f';

	poz_i=line.find_first_not_of(" ",10);	//get owner
	poz_f=line.find_first_of(" ",poz_i);
	poz_i=line.find_first_not_of(" ",poz_f);
	poz_f=line.find_first_of(" ",poz_i);
	poz_i=line.find_first_not_of(" ",poz_f);
	poz_f=line.find_first_of(" ",poz_i);

	poz_i=line.find_first_not_of(" ",poz_f);	//get size
	poz_f=line.find_first_of(" ",poz_i);

	if (t!='d')
		s=atoi(line.substr(poz_i,poz_f-poz_i).c_str());
	else
		s=0;

	poz_i=line.find_first_not_of(" ",poz_f);	//get last mod date
	poz_f=line.find_first_of(" ",poz_i);
	poz_i=line.find_first_not_of(" ",poz_f);
	poz_f=line.find_first_of(" ",poz_i);
	poz_i=line.find_first_not_of(" ",poz_f);
	poz_f=line.find_first_of(" ",poz_i);


	poz_i=line.find_first_not_of(" ",poz_f);	//get name
	poz_f=0;
	poz_f=line.find_first_of(" ",poz_i);

	n=line.substr(poz_i,poz_f-poz_i);

	if (poz_f!=-1){		//if not end of line
		t='m';
		poz_i=line.find_first_not_of(" ",poz_f);
		poz_f=line.find_first_of(" ",poz_i);
		poz_i=line.find_first_not_of(" ",poz_f);
		poz_f=line.find_first_of(" ",poz_i);
	}
	path=temp->get_path()+n;		//make full path for download
	if (t=='d' || t=='m'){
		path+="/";
		mdtm="";
	}
	else{
		send("MDTM",path,0);
		mdtm=get_last();
		int begin,end;
		begin=mdtm.find_first_of(' ',0)+1;
		end=mdtm.find_first_of(' ',begin);
		mdtm=mdtm.substr(begin,end);
	}

	if ((n!=".") && (n!="..")){
		temp->add_child(t,n,path,s,mdtm);	//add child to parent list
		return 0;
	}
	else
		return 1;

}


void ftp::cancel(){
	///Set flags so that any operation stops.

	stop=1;
	refr=0;
	dta.cancel();
}



